package com.xzzz.expand.es.core;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.xzzz.expand.es.core.common.EsException;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.client.GetAliasesResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.*;
import org.elasticsearch.cluster.metadata.AliasMetadata;
import org.elasticsearch.cluster.metadata.MappingMetadata;
import org.elasticsearch.common.xcontent.XContentType;

import java.io.IOException;
import java.util.Map;
import java.util.Set;

/**
 * 对索引进行操作
 * <ol>
 *
 * <li>
 *   索引
 *   <ul>
 *     <li>判断是否存在</li>
 *     <li>查询索引列表</li>
 *     <li>新增索引</li>
 *     <li>删除索引</li>
 *     <li>[TODO] 开启索引</li>
 *     <li>[TODO] 关闭索引</li>
 *   </ul>
 * </li>
 *
 * <li>
 *   映射
 *   <ul>
 *     <li>查询映射</li>
 *     <li>修改映射</li>
 *   </ul>
 * </li>
 *
 * </ol>
 */
@Slf4j
public class EsIndicesManager extends AbstractEsManager {

    public EsIndicesManager(RestHighLevelClient restHighLevelClient, EsProperties esProperties) {
        super(restHighLevelClient, esProperties);
    }


    /**
     * 判断索引是否存在
     *
     * @param index 索引名称
     * @return true:存在; false:不存在;
     */
    public boolean exists(String index) {
        GetIndexRequest request = new GetIndexRequest(index);
        try {
            return client.indices().exists(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new EsException("查询索引失败");
        }
    }

    /**
     * 获取全部 index 集合, 注意:该方法会查询到系统索引, 未来会默认禁止访问系统索引
     * [Kibana_1， .async-search， .kibana_task_manager_1， .apm-agent-configuration， .apm-custom-link]
     *
     * <pre>GET /_alias</pre>
     *
     * @return index 集合
     */
    public Set<String> getAliases(String... indices) {
        try {
            GetAliasesRequest request = new GetAliasesRequest();
            if (ArrayUtil.isNotEmpty(indices)) {
                request.indices(indices);
            }
            GetAliasesResponse getAliasesResponse = client.indices().getAlias(request, RequestOptions.DEFAULT);
            Map<String, Set<AliasMetadata>> map = getAliasesResponse.getAliases();
            return map.keySet();
        } catch (IOException e) {
            throw new EsException("查询索引失败");
        }
    }

    public Set<String> getAliases() {
        return getAliases(new String[]{});
    }


    /**
     * 创建索引
     *
     * <p>创建索引需要传入索引的 mappings 的配置信息, 即索引 mappings 的 properties 字段, 具体层级为：[mappings._doc.properties]
     * properties 的格式为 JSON 格式
     *
     * <p>例如：
     * <pre>String properties = "{\"properties\":{\"test_field\":{\"type\":\"text\",\"fields\":{\"keyword\":{\"type\":\"keyword\",\"ignore_above\":256}},\"analyzer\":\"ik_max_word\",\"search_analyzer\":\"ik_smart\"}}}";</pre>
     *
     * @param index      索引名称
     * @param properties 索引 mappings 的 properties 字段, 具体层级为：[mappings._doc.properties], 为 JSON 格式的字符串
     * @return 索引名称
     */
    public String createIndex(String index, String properties) {
        if (StrUtil.isBlank(index)) {
            throw new EsException("index 不得为空");
        }
        try {
            CreateIndexRequest request = new CreateIndexRequest(index);
            request.mapping(properties, XContentType.JSON);
            // request.settings() 创建索引时添加 settings
            // request.alias(); 创建索引时添加别名
            client.indices().create(request, RequestOptions.DEFAULT);
            return index;
        } catch (IOException e) {
            e.printStackTrace();
            throw new EsException("创建索引失败:" + e.getMessage());
        }
    }


    /**
     * 删除 index
     *
     * @param index 索引名称
     * @return 被删除的索引名称
     */
    public String deleteIndex(String index) {
        try {
            DeleteIndexRequest request = new DeleteIndexRequest(index);
            client.indices().delete(request, RequestOptions.DEFAULT);
            return index;
        } catch (IOException e) {
            e.printStackTrace();
            throw new EsException("创建索引失败:" + e.getMessage());
        }
    }


    /**
     * 获取 Index 的字段映射, 获取到的是索引 mappings 的 properties 字段, 具体层级为：[mappings._doc.properties]
     *
     * <pre>GET /xxx_index/_mapping</pre>
     *
     * @param index Index
     * @return 字段映射
     */
    public Map<String, Object> getMappings(String index) {
        try {
            GetMappingsRequest request = new GetMappingsRequest().indices(index);
            GetMappingsResponse res = client.indices().getMapping(request, RequestOptions.DEFAULT);
            Map<String, MappingMetadata> mapping = res.mappings();
            return mapping.get(index).getSourceAsMap();
        } catch (IOException e) {
            throw new EsException("查询错误");
        }
    }

    /**
     * 修改 Mapping
     *
     * <p>修改映射需要传入索引的 mappings 的配置信息, 即索引 mappings 的 properties 字段, 具体层级为：[mappings._doc.properties]
     * , properties 的格式为 JSON 格式
     *
     * <p>例如：
     * <pre>String properties = "{\"properties\":{\"test_field\":{\"type\":\"text\",\"fields\":{\"keyword\":{\"type\":\"keyword\",\"ignore_above\":256}},\"analyzer\":\"ik_max_word\",\"search_analyzer\":\"ik_smart\"}}}";</pre>
     *
     * <p>注意：
     * <ol>
     * <li>传入的 properties 与原 properties 相同的是可以修改成功的, 但映射不会发生变化</li>
     * <li>传入的 properties 包含原 properties 但字段更多是可以修改成功的, 多出的字段会新增到映射中</li>
     * <li>传入的 properties 不包含原 properties, 只包含新的字段是可以修改成功的, 新字段会新增到映射中</li>
     * <li>索引的字段是不允许删除的</li>
     * </ol>
     *
     * @param index      索引名称
     * @param properties 映射配置
     * @return 索引名称
     */
    public String putMappings(String index, String properties) {
        try {
            PutMappingRequest request = new PutMappingRequest(index);
            request.source(properties, XContentType.JSON);
            client.indices().putMapping(request, RequestOptions.DEFAULT);
            return index;
        } catch (IOException e) {
            throw new EsException("查询错误");
        }
    }


}
